package com.dy.分类.图._207课程表;
/*
现在你总共有 n 门课需要选，记为 0 到 n-1。

在选修某些课程之前需要一些先修课程。 例如，想要学习课程 0 ，你需要先完成课程 1 ，我们用一个匹配来表示他们: [0,1]

给定课程总量以及它们的先决条件，判断是否可能完成所有课程的学习？

示例 1:

输入: 2, [[1,0]]
输出: true
解释: 总共有 2 门课程。学习课程 1 之前，你需要完成课程 0。所以这是可能的。
示例 2:

输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前，你需要先完成​课程 0；并且学习课程 0 之前，你还应先完成课程 1。这是不可能的。
说明:

输入的先决条件是由边缘列表表示的图形，而不是邻接矩阵。详情请参见图的表示法。
你可以假定输入的先决条件中没有重复的边。
提示:

这个问题相当于查找一个循环是否存在于有向图中。如果存在循环，则不存在拓扑排序，因此不可能选取所有课程进行学习。
通过 DFS 进行拓扑排序 - 一个关于Coursera的精彩视频教程（21分钟），介绍拓扑排序的基本概念。
拓扑排序也可以通过 BFS 完成。
 */

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

/**
 * 思路：
 * 构建图，判断是否有环，有环则失败
 * 方法1：dfs ，深度搜索，三种颜色，初始化为白色，搜索过程中灰色，回退后如果都遍历了，就染黑
 */
class GraphNode {
    int course;
    int degree;//入度
    ArrayList<GraphNode> neighbors;

    public GraphNode(int course) {
        this.course = course;
        neighbors= new ArrayList();
    }
}
//0代表白色
public class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        if(numCourses==0) return true;
        int visit[] = new int[numCourses];
        List<GraphNode> nodes = new ArrayList<>();
        //建点
        for (int i = 0; i < numCourses; i++) {
            visit[i]=0;
            nodes.add(new GraphNode(i));
        }
        //建边
        for(int i=0;i<prerequisites.length;i++){
            GraphNode begin = nodes.get(prerequisites[i][0]);
            GraphNode end = nodes.get(prerequisites[i][1]);
            begin.neighbors.add(end);
        }
        for(int i=0;i<numCourses;i++){
            if(visit[i]==0){
                if(DFS(nodes.get(i),visit)==false){
                    return false;
                }
            }
        }
        return true;
    }
    boolean DFS(GraphNode node,int visit[]){
        //表示第一次路过标记为1
        visit[node.course] = 1;

        for(int i=0;i<node.neighbors.size();i++){
            GraphNode ni = node.neighbors.get(i);
            if(visit[ni.course]==0){
                if(DFS(ni,visit)==false){
                    return false;
                }
                //当邻接节点有为1的
            }else if(visit[ni.course]==1){
                return false;
            }
        }

        //表示已经深搜完成标记为2
        visit[node.course] =2;
        return true;
    }
    //方法二：拓扑排序，宽度搜索
    public boolean canFinish2(int numCourses, int[][] prerequisites){
        List<GraphNode> nodes = new ArrayList<>();
        for(int i=0;i<numCourses;i++){
            nodes.add(new GraphNode(i));
        }
        for(int i=0;i<prerequisites.length;i++){
            GraphNode begin = nodes.get(prerequisites[i][0]);
            GraphNode end = nodes.get(prerequisites[i][1]);
            begin.neighbors.add(end);
            end.degree++;
        }
        //先把入度为0的点放入队列
        Queue<GraphNode> q = new LinkedList<>();
        for(int i=0;i<numCourses;i++){
            GraphNode node=nodes.get(i);
            if(node.degree==0){
                q.add(node);
            }
        }
        while(!q.isEmpty()){
            GraphNode node = q.poll();
            for(int i=0;i<node.neighbors.size();i++){
                GraphNode neighbor = node.neighbors.get(i);
                neighbor.degree--;
                if(neighbor.degree==0){
                    q.add(neighbor);
                }
            }
        }
        for(int i=0;i<numCourses;i++){
            GraphNode node=nodes.get(i);
            if(node.degree!=0){
                return false;
            }
        }
        return true;
    }
}

